home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Nebula 1
/
Nebula One.iso
/
Financial
/
Stopwatch2.3
/
Source
/
ClientInfo.m
< prev
next >
Wrap
Text File
|
1995-06-12
|
8KB
|
424 lines
/*
* For legal stuff see the file COPYRIGHT
*/
#import <stdlib.h>
#import <objc/hashtable.h>
#import "ClientInfo.h"
#import "Controller.h"
#import "Session.h"
#import "Expense.h"
@interface ClientInfo(PRIVATE)
- (void)setupSessionList;
- (void)setupExpenseList;
@end
@implementation ClientInfo
- init
{
[super init];
[self setupSessionList];
[self setupExpenseList];
return self;
}
- free
{
[[sessionList freeObjects] free];
sessionList = nil;
[[expenseList freeObjects] free];
expenseList = nil;
return [super free];
}
- (void)computeTotalMins
{
int i, count = [sessionList count];
totalMins = 0;
for ( i = 0; i < count; i++ )
totalMins += [[sessionList objectAt:i] minutes];
}
/*
* Called by the controller when a new session has been started.
*/
- addSession:session
{
[sessionList addObject:session];
totalMins += [session minutes];
return self;
}
- deleteSession:session
{
totalMins -= [session minutes];
return [sessionList removeObject:session];
}
/*
* Delete all session and expense data
*/
- (void)deleteSessionsAndExpenses
{
[sessionList freeObjects];
[expenseList freeObjects];
totalMins = 0.0;
}
- (const char *)lastDescription
{
const char *str = [[sessionList objectAt:0] description];
return str ? str : "";
}
- (int)sessionCount
{
return [sessionList count];
}
- sessionAt:(int)position
{
return [sessionList objectAt:position];
}
/*
* Called by the controller when a new session has been started.
*/
- addExpense:expense
{
[expenseList addObject:expense];
return self;
}
- deleteExpense:expense
{
return [expenseList removeObject:expense];
}
- (void)deleteAllExpenses
{
[expenseList freeObjects];
}
- (int)expenseCount
{
return [expenseList count];
}
- expenseAt:(int)position
{
return [expenseList objectAt:position];
}
- (void)sortSessions
{
[sessionList sort];
}
- (void)sortExpenses
{
[expenseList sort];
}
- (const char *)shortName
{
return shortName;
}
- (const char *)clientName
{
return clientName;
}
- (const char *)contactName
{
return contactName;
}
- (const char *)street
{
return street;
}
- (const char *)city
{
return city;
}
- (const char *)addrState /* "state" is a defined method in Cell...*/
{
return state;
}
- (const char *)zipCode
{
return zipCode;
}
- (const char *)faxNumber
{
return faxNumber;
}
- (const char *)emailAddr
{
return emailAddr;
}
- (float)hourlyRate
{
return hourlyRate;
}
- (int)totalMins
{
return totalMins;
}
/*
* This is necessary to have the number we use in calculations be
* (almost) identical to that which is printed on the invoices. It
* turns out for a total number of hours such as 76.466667, which
* prints and displays as 76.47 causes enough error when multiplied
* by an hourly rate that clients noticed the difference. (It happened
* to be in their favor. This adjusts it to remove the discrepancy.
* It happens to result in a few more cents in our favor, by the way!
*/
- (float)totalHours
{
return ((int)((totalMins * 100)/60 + 0.5))/100.0; /* round up then truncate */
}
- (float)totalBillable
{
return hourlyRate * [self totalHours];
}
/*
* Compute this on the fly.
*/
- (float)totalExpenses
{
int i, count = [expenseList count];
float amount = 0.0;
for ( i = 0; i < count; i++ ) {
Expense *expense = [expenseList objectAt:i];
amount += [expense amount];
}
return amount;
}
- setShortName:(const char *)str
{
freeAndCopy( &shortName, str );
return self;
}
- setClientName:(const char *)str
{
freeAndCopy( &clientName, str );
return self;
}
- setContactName:(const char *)str
{
freeAndCopy( &contactName, str );
return self;
}
- setStreet:(const char *)str
{
freeAndCopy( &street, str );
return self;
}
- setCity:(const char *)str
{
freeAndCopy( &city, str );
return self;
}
- setAddrState:(const char *)str
{
freeAndCopy( &state, str );
return self;
}
- setZipCode:(const char *)str
{
freeAndCopy( &zipCode, str );
return self;
}
- setFaxNumber:(const char *)str
{
freeAndCopy( &faxNumber, str );
return self;
}
- setEmailAddr:(const char *)str
{
freeAndCopy( &emailAddr, str );
return self;
}
- setHourlyRate:(float)value
{
hourlyRate = value;
return self;
}
- setTotalMins:(int)value
{
totalMins = value;
return self;
}
/*
* Compress out consecutive sessions with identical descriptions.
* This method assumes the list stores sessions in reverse
* chronological order.
*/
- (void)compactSessions
{
int i, earliestPos = [sessionList count] - 1;
Session *earliest = [sessionList objectAt:earliestPos];
for ( i = earliestPos - 1; i >= 0; --i ) {
Session *session;
session = [sessionList objectAt:i];
/* Check for consecutive sessions on the same date with the same text */
if ( strcmp([session description], [earliest description]) == 0 &&
strcmp([session startDateString], [earliest startDateString]) == 0 ) {
[earliest setMinutes:[earliest minutes] + [session minutes]];
[[sessionList removeObjectAt:i] free];
} else
earliest = session; /* this is our new base case */
}
}
- (void)exportToFile:(FILE *)fp
{
int i, count = [sessionList count];
for ( i = 0; i < count; i++ ) {
Session *session;
session = [sessionList objectAt:i];
fprintf( fp, "%s%c%s%c%s%c%d%c%s\n",
shortName, DELIMITER,
[session startDateString], DELIMITER,
[session startTimeString], DELIMITER,
[session minutes], DELIMITER,
[session description] );
}
}
- read: (NXTypedStream *) stream
{
extern int FileVersion;
switch ( FileVersion ) {
case 0:
NXReadTypes( stream, "********f",
&clientName, &contactName, &street, &city, &state, &zipCode,
&faxNumber, &emailAddr, &hourlyRate );
/* make the short name the same as the full name initially */
shortName = NXCopyStringBuffer(clientName);
break;
default:
/* Version 1 added shortName for client */
NXReadTypes( stream, "*********f",
&shortName, &clientName, &contactName,
&street, &city, &state, &zipCode,
&faxNumber, &emailAddr, &hourlyRate );
break;
}
if ( ! sessionList )
[self setupSessionList];
[sessionList read:stream];
[sessionList sort];
if ( ! expenseList )
[self setupExpenseList];
if ( FileVersion >= 2 ) {
[expenseList read:stream];
[expenseList sort];
}
[self computeTotalMins];
return self;
}
- write:(NXTypedStream *) stream
{
NXWriteTypes( stream, "*********f",
&shortName, &clientName, &contactName,
&street, &city, &state, &zipCode,
&faxNumber, &emailAddr, &hourlyRate );
[sessionList write:stream];
[expenseList write:stream];
return self;
}
@end
@implementation ClientInfo(PRIVATE)
/*
* Compare the dates (and if equal, the times) of two
* sessions and return the proper value to achieve a
* reverse chronological sort.
*/
- (int)compareSessions:(Session *)obj1 :(Session *)obj2
{
int date1 = [obj1 dateValue], date2 = [obj2 dateValue];
if ( date1 == date2 )
return ( [obj1 timeValue] > [obj2 timeValue] ? -1 : 1 );
return ( date1 > date2 ? -1 : 1 );
}
- (int)compareExpenses:(Expense *)obj1 :(Expense *)obj2
{
int date1 = [obj1 dateValue], date2 = [obj2 dateValue];
return ( date1 > date2 ? -1 : 1 );
}
- (void)setupSessionList
{
if ( ! sessionList ) {
sessionList = [[SortList alloc] init];
[sessionList setDelegate:self];
[sessionList setAutoSort:YES];
[sessionList useComparisonMethod:@selector(compareSessions::)];
}
}
- (void)setupExpenseList
{
if ( ! expenseList ) {
expenseList = [[SortList alloc] init];
[expenseList setDelegate:self];
[expenseList setAutoSort:YES];
[expenseList useComparisonMethod:@selector(compareExpenses::)];
}
}
@end